package com.rpc.example.spring.customer;

import com.rpc.example.annotation.RpcRemoteReference;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
public class RpcReferencePostProcessor implements ApplicationContextAware, BeanClassLoaderAware, BeanFactoryPostProcessor {
    private ClassLoader classLoader;
    private ApplicationContext applicationContext;

    private RpcReferenceConfiguration configuration;

    public RpcReferencePostProcessor(RpcReferenceConfiguration configuration) {
        this.configuration = configuration;
    }

    private Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();

    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }


    public RpcReferencePostProcessor() {
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        for (String beanDefinitionName : beanFactory.getBeanDefinitionNames()) {
            BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanDefinitionName);
            String beanClassName = beanDefinition.getBeanClassName();
            if (beanClassName != null) {
                Class<?> aClass = ClassUtils.resolveClassName(beanClassName, this.classLoader);
                ReflectionUtils.doWithFields(aClass, this::revsovleReferenceBean);
            }
        }

        BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
        beanDefinitionMap.forEach((key, value) -> {
            if (applicationContext.containsBean(key)) {
                log.info("{} has registered!!", key);
                return;
            }
            registry.registerBeanDefinition(key, value);
        });
     }

    private void revsovleReferenceBean(Field field) {
        RpcRemoteReference annotation = AnnotationUtils.getAnnotation(field, RpcRemoteReference.class);
        if (annotation != null) {
            BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(RpcReferenceBean.class);
            builder.setInitMethodName("init");
            builder.addPropertyValue("registerAddress", configuration.getRegisterAddress());
            builder.addPropertyValue("registryType", configuration.getRegistryType());
            builder.addPropertyValue("applicationName", annotation.value());
            builder.addPropertyValue("clazz", field.getType());
            beanDefinitionMap.put(field.getName(), builder.getBeanDefinition());
        }

    }
}
